﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using XcoAppSpaces.Core.Ports.Remoting;

namespace XcoAppSpaces.Core.Ports.Utils
{
	/// <summary>
	/// Represents a single element in a port. Contains everything that is required to process the item.
	/// </summary>
	public class PortElement<T> : IPortElement
	{
		/// <summary>
		/// The item that was posted to the port.
		/// </summary>
		public T Item { get; set; }

		/// <summary>
		/// The list of causalities that was active when item has been posted.
		/// </summary>
		public List<ICausality> Causalities { get; set; }

		/// <summary>
		/// A TaskCompletionSource, which allows to asynchronously signal error or completion of the item.
		/// </summary>
		public TaskCompletionSource<object> SentCompletionSource { get; set; }

		/// <summary>
		/// The item that has been posted to the port, as an object.
		/// </summary>
		public object ItemObj { get { return Item; } }

		/// <summary>
		/// Provides additional information about the message (e.g. address of sender), if it is a remote message.
		/// </summary>
		public MessageContext MessageContext { get; set; }

		/// <summary>
		/// Signals that a message has been sent successfully (completes the sent completion source, posts an ack to the ack port).
		/// </summary>
		public void SignalSent()
		{
			if (SentCompletionSource != null)
				SentCompletionSource.TrySetResult(null);
			if (Causalities != null)
			{
				var c = Causalities.LastOrDefault();
				if (c != null && c.AckPort != null)
					c.AckPort.Post(new Ack(DateTime.Now));
			}
		}

		/// <summary>
		/// Signals a processing error. This leads to the exception being posted to the exception port, or the TaskCompletionSource being marked as faulted.
		/// If no causality or TaskCompletionSource exists, then the error ist posted to the unhandled error port.
		/// </summary>
		/// <param name="ex">The excepction was thrown.</param>
		/// <returns>True if the error was handled by a causality or TaskCompletionSource, otherwise false.</returns>
		public bool SignalError(Exception ex)
		{
			bool errorHandled = false;
			if (SentCompletionSource != null)
			{
				SentCompletionSource.TrySetException(ex);
				errorHandled = true;
			}
			else if (Causalities != null) //post to causality only when there is no completion source (to avoid double-handling errors)
			{
				var c = Causalities.LastOrDefault();
				if (c != null && c.ExceptionPort != null)
				{
					c.ExceptionPort.Post(ex);
					errorHandled = true;
				}
			}
			
			if (!errorHandled)
				CausalityContext.UnhandledExceptionPort.Post(ex);
			return errorHandled;
		}
	}
}
